﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[ExecuteInEditMode]
public class JohnnyUIPieChart : MonoBehaviour {

	public float Radius = 2f;
	public float HoleRadius = 1f;
	public int Segments = 128;
	public float Height = 2f;
	public float ExplodedRadius = 0.5f;
	public float TextRadius = 3f;
	public TextMesh TextTemplate;

	public float FilletSegments = 6;
	public float ChamferFilletLength = 0.25f;

	Mesh pieChart = null;
	public MeshFilter PieChartFilter;
	public LineRenderer PieChartLineRenderer;

	public PieChartValueCollection PieChartValues;

	//public Material PieChartMainMaterial;

	Vector3 PointAtRadiusAngle(float angle, float radius, Vector3 center)
	{
		return new Vector3(center.x + (radius * Mathf.Cos(angle * Mathf.Deg2Rad)), center.y + (radius * Mathf.Sin(angle * Mathf.Deg2Rad)), center.z);
	}

	// Use this for initialization
	void Start () {
		this.DrawCircle();
	}
	
	// Update is called once per frame
	void Update () {
		#if UNITY_EDITOR
		if (!Application.isPlaying) {
			UnityEditor.EditorUtility.SetDirty(this);
			this.DrawCircle();
		}
		#endif
	}

	void DrawCircle()
	{
		for (int z=this.transform.childCount-1;z>=0;z--) {
			Transform trans = this.transform.GetChild(z);
			if (trans.name.StartsWith("PieText_")) {
				DestroyImmediate(trans.gameObject);
			}
		}

		pieChart = new Mesh();
		pieChart.Clear();
		//pieChart.Clear();

		Vector3 centerPosition = new Vector3(0f,0f,0f);
		Vector3 [] innerMeshPoints = new Vector3[this.Segments];
		Vector3 [] outerMeshPoints = new Vector3[this.Segments];
		Vector3 [] innerMeshPointsFillet = new Vector3[this.Segments];
		Vector3 [] outerMeshPointsFillet = new Vector3[this.Segments];

		float chamferInternalSideLength = Mathf.Sqrt(this.ChamferFilletLength / 2f);

		float halfHeight = this.Height / 2.0f;
		float heightTop = centerPosition.z + halfHeight;
		float heightBottom = centerPosition.z - halfHeight;
		float heightTopFillet = centerPosition.z + halfHeight - chamferInternalSideLength;
		float heightBottomFillet = centerPosition.z - halfHeight + chamferInternalSideLength;

		List<int> triangles = new List<int>();
		List<Vector3> verts = new List<Vector3>();
		List<Vector2> uvs = new List<Vector2>();
		List<Color> colors = new List<Color>();

		List<Vector3> positions = new List<Vector3>();

		int vertex = 0;
		int currentMesh = 0;
		for (int z=0;z<this.Segments;z++)
		{
			float angle = (float)z / (float)this.Segments * 360.0f;
			innerMeshPoints[z] = this.PointAtRadiusAngle(angle, this.HoleRadius, centerPosition);
			outerMeshPoints[z] = this.PointAtRadiusAngle(angle, this.Radius, centerPosition);
			innerMeshPointsFillet[z] = this.PointAtRadiusAngle(angle, this.HoleRadius + chamferInternalSideLength, centerPosition);
			outerMeshPointsFillet[z] = this.PointAtRadiusAngle(angle, this.Radius - chamferInternalSideLength, centerPosition);
			//Debug.Log(outerMeshPoints[z]);
		}

		currentMesh = 0;
		int subMeshes = 1;
		int [] subMeshStarts;
		int [] subMeshEnds;
		int [] subMeshMidpoints;
		Vector3 [] subMeshExplodedRadius;
		if (this.PieChartValues == null) {
			subMeshStarts = new int[1] {0};
			// My chosen way of handling midpoints is to not show exploded pie slices if
			// you have only one segment.
			subMeshMidpoints = new int[1] {0};
			subMeshExplodedRadius = new Vector3[1] {new Vector3(0f,0f,0f)};
			subMeshEnds = new int[1] {this.Segments-1};
		}
		else {
			this.PieChartValues.CalculateValues();
			subMeshStarts = new int[PieChartValues.UsedPercentages.Length];
			subMeshMidpoints = new int[PieChartValues.UsedPercentages.Length];
			subMeshExplodedRadius = new Vector3[PieChartValues.UsedPercentages.Length];
			subMeshEnds = new int[PieChartValues.UsedPercentages.Length];
			subMeshes = PieChartValues.UsedPercentages.Length;

			int currentPosition = 0;
			for (int z=0;z<PieChartValues.UsedPercentages.Length;z++) {

				subMeshStarts[z] = currentPosition;
				currentPosition += (int)PieChartValues.UsedPercentages[z] * 3 - 1;
				subMeshEnds[z] = currentPosition;
				currentPosition++;
				subMeshMidpoints[z] = (subMeshStarts[z] + subMeshEnds[z]) / 2;

				float angle = (float)subMeshMidpoints[z] / (float)this.Segments * 360.0f;
				subMeshExplodedRadius[z] = this.PointAtRadiusAngle(angle, ExplodedRadius, centerPosition);

				TextAnchor anchor = TextAnchor.UpperLeft;
				if (angle == 0f) {
					anchor = TextAnchor.MiddleLeft;
				}
				else if (angle == 90f) {
					anchor = TextAnchor.LowerCenter;
				}
				else if (angle == 180f) {
					anchor = TextAnchor.MiddleRight;
				}
				else if (angle == 270f) {
					anchor = TextAnchor.UpperCenter;
				}
				else if (angle > 0f && angle < 90f) {
					anchor = TextAnchor.LowerLeft;
				}
				else if (angle > 90f && angle < 180f) {
					anchor = TextAnchor.LowerRight;
				}
				else if (angle > 180f && angle < 270f) {
					anchor = TextAnchor.UpperRight;
				}
				else if (angle > 270f && angle < 360f) {
					anchor = TextAnchor.UpperLeft;
				}

				Vector3 textPoint = this.PointAtRadiusAngle(angle, TextRadius + PieChartValues.PieChartValues[z].DataExtraWidth, centerPosition);
				Vector3 lastScale = this.TextTemplate.transform.localScale;
				TextMesh newMesh = (TextMesh)Instantiate(this.TextTemplate);
				newMesh.transform.rotation = this.transform.rotation;
				newMesh.transform.position = this.transform.position;
				newMesh.transform.Translate(textPoint);

				newMesh.anchor = anchor;
				string formattedText = string.Format("{0}{3}{1} ({2:N2}%)", 
				                                     PieChartValues.PieChartValues[z].DataName,
				                                     PieChartValues.PieChartValues[z].DataValue,
				                                     PieChartValues.Percentages[z],
				                                     System.Environment.NewLine);
				newMesh.text = formattedText;
				newMesh.transform.name = "PieText_"+z;
				newMesh.transform.parent = this.gameObject.transform;
			}


		}

		int currentMeshPosition = 0;
		for (int z=0;z<this.Segments;z++) {
			bool isSubMeshStart = false;
			bool isSubMeshEnd = false;
			if (z > subMeshEnds[currentMesh]) {
				currentMesh++;
				currentMeshPosition++;
			} 
			
			if (z == subMeshStarts[currentMesh]) {
				currentMeshPosition = 0;
				isSubMeshStart = true;
			} else if (z == subMeshEnds[currentMesh]) {
				isSubMeshEnd = true;
			} 

			if (PieChartValues.PieChartValues[currentMesh].DataExtraWidth != 0f) {
				float angle = (float)z / (float)this.Segments * 360.0f;
				outerMeshPoints[z] = this.PointAtRadiusAngle(angle, this.Radius + PieChartValues.PieChartValues[currentMesh].DataExtraWidth, centerPosition);
			}
		}

		currentMesh = 0;
		currentMeshPosition = 0;
		pieChart.subMeshCount = subMeshes;
		Dictionary<int, List<int>> subTriangles = new Dictionary<int, List<int>>();
		for (int z=0;z<subMeshes;z++) {
			subTriangles[z] = new List<int>();
		}
		for (int z=0;z<this.Segments;z++)
		{
			bool isSubMeshStart = false;
			bool isSubMeshEnd = false;
			if (z > subMeshEnds[currentMesh]) {
				currentMesh++;
				currentMeshPosition++;
			} 

			if (z == subMeshStarts[currentMesh]) {
				currentMeshPosition = 0;
				isSubMeshStart = true;
			} else if (z == subMeshEnds[currentMesh]) {
				isSubMeshEnd = true;
			} 


			int nextZ = z+1;
			if (nextZ == this.Segments) nextZ = 0;
			if (isSubMeshEnd) {
				nextZ = z;
			}

			float lastHeightTop = heightTop;
			float lastHeightBottom = heightBottom;

			if (PieChartValues.PieChartValues[currentMesh].DataExtraHeight != 0f) {
				heightTop += PieChartValues.PieChartValues[currentMesh].DataExtraHeight / 2f;
				heightBottom -= PieChartValues.PieChartValues[currentMesh].DataExtraHeight / 2f;
			}

			// Construct each quad strip face within the hole
			Vector3 outsideFaceUpperLeft = new Vector3(outerMeshPoints[z].x, outerMeshPoints[z].y, heightTop) + subMeshExplodedRadius[currentMesh];
			Vector3 outsideFaceUpperRight = new Vector3(outerMeshPoints[nextZ].x, outerMeshPoints[nextZ].y, heightTop) + subMeshExplodedRadius[currentMesh];
			Vector3 outsideFaceLowerLeft = new Vector3(outerMeshPoints[z].x, outerMeshPoints[z].y, heightBottom) + subMeshExplodedRadius[currentMesh];
			Vector3 outsideFaceLowerRight = new Vector3(outerMeshPoints[nextZ].x, outerMeshPoints[nextZ].y, heightBottom) + subMeshExplodedRadius[currentMesh];

			Vector3 innerFaceUpperLeft = new Vector3(innerMeshPoints[z].x, innerMeshPoints[z].y, heightTop) + subMeshExplodedRadius[currentMesh];
			Vector3 innerFaceUpperRight = new Vector3(innerMeshPoints[nextZ].x, innerMeshPoints[nextZ].y, heightTop) + subMeshExplodedRadius[currentMesh];
			Vector3 innerFaceLowerLeft = new Vector3(innerMeshPoints[z].x, innerMeshPoints[z].y, heightBottom) + subMeshExplodedRadius[currentMesh];
			Vector3 innerFaceLowerRight = new Vector3(innerMeshPoints[nextZ].x, innerMeshPoints[nextZ].y, heightBottom) + subMeshExplodedRadius[currentMesh];

			heightTop = lastHeightTop;
			heightBottom = lastHeightBottom;

			Vector3 insideFaceUpperLeft = Vector3.zero;
			Vector3 insideFaceUpperRight = Vector3.zero;
			Vector3 insideFaceLowerLeft = Vector3.zero;
			Vector3 insideFaceLowerRight = Vector3.zero;

			if (isSubMeshStart) {
				insideFaceUpperLeft = outsideFaceUpperLeft;
				insideFaceUpperRight = innerFaceUpperLeft;
				insideFaceLowerLeft = outsideFaceLowerLeft;
				insideFaceLowerRight = innerFaceLowerLeft;
			} 
			if (isSubMeshEnd) {
				insideFaceUpperLeft = outsideFaceUpperRight;
				insideFaceUpperRight = innerFaceUpperRight;
				insideFaceLowerLeft = outsideFaceLowerRight;
				insideFaceLowerRight = innerFaceLowerRight;
			}


			//positions.Add(outsideFaceLowerLeft);

			//Debug.Log(outsideFaceUpperLeft.ToString() + " / " + outsideFaceLowerRight.ToString());
			//Debug.Log(innerFaceUpperLeft.ToString() + " / " + innerFaceLowerRight.ToString());

			int[] insideFirstQuad = null;
			int[] reverseInsideFirstQuad = null;

			if (isSubMeshStart) {
				insideFirstQuad = new int[] {
					vertex + 0, vertex + 1, vertex + 2,
					vertex + 1, vertex + 3, vertex + 2
				};
				vertex += 4;
				reverseInsideFirstQuad = new int[] {
					vertex + 2, vertex + 1, vertex + 0,
					vertex + 2, vertex + 3, vertex + 1
				};
				vertex += 4;
			}

			int[] firstQuad = new int[] {
				vertex + 2, vertex + 1, vertex + 0,
				vertex + 2, vertex + 3, vertex + 1
			};
			vertex += 4;
			int[] reverseFirstQuad = new int[] {
				vertex + 0, vertex + 1, vertex + 2,
				vertex + 1, vertex + 3, vertex + 2
			};
			vertex += 4;


			int[] secondQuad = new int[] {
				vertex + 0, vertex + 1, vertex + 2,
				vertex + 1, vertex + 3, vertex + 2
			};
			vertex += 4;
			int[] reverseSecondQuad = new int[] {
				vertex + 2, vertex + 1, vertex + 0,
				vertex + 2, vertex + 3, vertex + 1
			};
			vertex += 4;

			int[] thirdQuad = new int[] {
				vertex + 0, vertex + 1, vertex + 2,
				vertex + 1, vertex + 3, vertex + 2
			};
			vertex += 4;
			int[] reverseThirdQuad = new int[] {
				vertex + 2, vertex + 1, vertex + 0,
				vertex + 2, vertex + 3, vertex + 1
			};
			vertex += 4;

			int[] fourthQuad = new int[] {
				vertex + 2, vertex + 1, vertex + 0,
				vertex + 2, vertex + 3, vertex + 1
			};
			vertex += 4;
			int[] reverseFourthQuad = new int[] {
				vertex + 0, vertex + 1, vertex + 2,
				vertex + 1, vertex + 3, vertex + 2
			};
			vertex += 4;

			int[] insideLastQuad = null;
			int[] reverseInsideLastQuad = null;

			if (isSubMeshEnd) {
				insideLastQuad = new int[] {
					vertex + 2, vertex + 1, vertex + 0,
					vertex + 2, vertex + 3, vertex + 1
				};
				vertex += 4;
				reverseInsideLastQuad = new int[] {
					vertex + 0, vertex + 1, vertex + 2,
					vertex + 1, vertex + 3, vertex + 2
				};
				vertex += 4;
			}

			if (isSubMeshStart) {
				verts.AddRange(new Vector3[] { insideFaceUpperLeft, insideFaceUpperRight, insideFaceLowerLeft, insideFaceLowerRight});
				verts.AddRange(new Vector3[] { insideFaceUpperLeft, insideFaceUpperRight, insideFaceLowerLeft, insideFaceLowerRight});
			}
			verts.AddRange(new Vector3[] { outsideFaceUpperLeft, outsideFaceUpperRight, outsideFaceLowerLeft, outsideFaceLowerRight});
			verts.AddRange(new Vector3[] { outsideFaceUpperLeft, outsideFaceUpperRight, outsideFaceLowerLeft, outsideFaceLowerRight});

			verts.AddRange(new Vector3[] { outsideFaceUpperLeft, outsideFaceUpperRight, innerFaceUpperLeft, innerFaceUpperRight });
			verts.AddRange(new Vector3[] { outsideFaceUpperLeft, outsideFaceUpperRight, innerFaceUpperLeft, innerFaceUpperRight });

			verts.AddRange(new Vector3[] { innerFaceUpperLeft, innerFaceUpperRight, innerFaceLowerLeft, innerFaceLowerRight});
			verts.AddRange(new Vector3[] { innerFaceUpperLeft, innerFaceUpperRight, innerFaceLowerLeft, innerFaceLowerRight});

			verts.AddRange(new Vector3[] { outsideFaceLowerLeft, outsideFaceLowerRight, innerFaceLowerLeft, innerFaceLowerRight });
			verts.AddRange(new Vector3[] { outsideFaceLowerLeft, outsideFaceLowerRight, innerFaceLowerLeft, innerFaceLowerRight });
			if (isSubMeshEnd) {
				verts.AddRange(new Vector3[] { insideFaceUpperLeft, insideFaceUpperRight, insideFaceLowerLeft, insideFaceLowerRight});
				verts.AddRange(new Vector3[] { insideFaceUpperLeft, insideFaceUpperRight, insideFaceLowerLeft, insideFaceLowerRight});
			}

			if (isSubMeshStart) {
				uvs.AddRange(new Vector2[] {new Vector2(0f,0f), new Vector2(0f,1f), new Vector2(1f,0f), new Vector2(1f,1f)});
				uvs.AddRange(new Vector2[] {new Vector2(0f,0f), new Vector2(0f,1f), new Vector2(1f,0f), new Vector2(1f,1f)});
			}

			float startUv = currentMeshPosition / (subMeshEnds[currentMesh] - subMeshStarts[currentMesh]);
			float endUv = (currentMeshPosition + 1) / (subMeshEnds[currentMesh] - subMeshStarts[currentMesh]);

			// Outside Faces
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});

			// Outside to Inner Upper
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});

			// Inner Faces
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});

			// Outside to Inner Lower
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});
			uvs.AddRange(new Vector2[] {new Vector2(0f,startUv), new Vector2(0f,endUv), new Vector2(1f,startUv), new Vector2(1f,endUv)});

			if (isSubMeshEnd) { 
				uvs.AddRange(new Vector2[] {new Vector2(0f,0f), new Vector2(0f,1f), new Vector2(1f,0f), new Vector2(1f,1f)});
				uvs.AddRange(new Vector2[] {new Vector2(0f,0f), new Vector2(0f,1f), new Vector2(1f,0f), new Vector2(1f,1f)});
			}

			if (isSubMeshStart) {
				colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
				colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			}
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			if (isSubMeshEnd) {
				colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
				colors.AddRange(new Color[] {new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f), new Color(1f,1f,1f)});
			}

			if (isSubMeshStart) {
				subTriangles[currentMesh].AddRange(insideFirstQuad);
				subTriangles[currentMesh].AddRange(reverseInsideFirstQuad);
			}
			subTriangles[currentMesh].AddRange(firstQuad);
			subTriangles[currentMesh].AddRange(reverseFirstQuad);
			subTriangles[currentMesh].AddRange(secondQuad);
			subTriangles[currentMesh].AddRange(reverseSecondQuad);
			subTriangles[currentMesh].AddRange(thirdQuad);
			subTriangles[currentMesh].AddRange(reverseThirdQuad);
			subTriangles[currentMesh].AddRange(fourthQuad);
			subTriangles[currentMesh].AddRange(reverseFourthQuad);
			if (isSubMeshEnd) {
				subTriangles[currentMesh].AddRange(insideLastQuad);
				subTriangles[currentMesh].AddRange(reverseInsideLastQuad);
			}

			/*
			triangles.AddRange(firstQuad);
			triangles.AddRange(secondQuad);
			triangles.AddRange(thirdQuad);
			triangles.AddRange(fourthQuad);
			*/
		}

		pieChart.vertices = verts.ToArray();
		pieChart.colors = colors.ToArray();
		//pieChart.triangles = triangles.ToArray();
		for (int z=0;z<subMeshes;z++) {
			pieChart.SetTriangles(subTriangles[z].ToArray(),z);
		}
		pieChart.uv = uvs.ToArray();
		pieChart.RecalculateNormals();
		Vector3[] normals = pieChart.normals;
		/*
		for(int z=0;z<normals.Length;z++) {
			normals[z] = -normals[z];
		}
		*/
		pieChart.normals = normals;
		
		PieChartFilter.mesh = pieChart;
		//pieChart.Clear();
		//DestroyImmediate (pieChart);
		
		/*
		PieChartLineRenderer.SetVertexCount(positions.Count);
		for (int z=0;z<positions.Count;z++) {
			PieChartLineRenderer.SetPosition(z, positions[z]);
		}
		*/
	}
}
